/**
* Copyright (c) By zengqh.
*
* This program is just for fun or demo, in the hope that it  
* will be useful, you can redistribute it and/or modify freely.
*
* Time: 2013/03/06
* File: OGLRenderSystem.cpp
**/

#include "OGLRenderSystem.h"
#include "OGLTexture2D.h"
#include "OGLTextureCube.h"
#include "OGLVertexBuffer.h"
#include "OGLIndexBuffer.h"
#include "OGLVertexShader.h"
#include "OGLPixelShader.h"
#include "OGLRenderTarget2D.h"
#include "OGLRenderTargetCube.h"
#include "OGLProgram.h"

namespace HY
{
Texture2D* OGLRenderSystem::createTexture2D()
{
	return new OGLTexture2D();
}

TextureCube* OGLRenderSystem::createTextureCube()
{
	return new OGLTextureCube();
}

VertexBuffer* OGLRenderSystem::createVertexBuffer()
{
	return new OGLVertexBuffer();
}

IndexBuffer* OGLRenderSystem::createIndexBuffer()
{
	return new OGLIndexBuffer();
}

RenderTarget2D* OGLRenderSystem::createRenderTarget2D()
{
	return new OGLRenderTarget2D();
}

RenderTargetCube* OGLRenderSystem::createRenderTargetCube()
{
	return new OGLRenderTargetCube();
}

VertexShader* OGLRenderSystem::createVertexShader()
{
	return new OGLVertexShader();
}

PixelShader* OGLRenderSystem::createPixelShader()
{
	return new OGLPixelShader();
}

Program* OGLRenderSystem::createProgram()
{
	return new OGLProgram();
}

uint32 OGLRenderSystem::getRenderState(uint32 state) const
{
	return render_state_[state];
}

bool OGLRenderSystem::setRenderState(uint32 state, uint32 value)
{
	if (render_state_[state] != value)
	{
		render_state_[state] = value;

		switch (state)
		{
		case RenderState::FillMode:
			{
				glPolygonMode(GL_FRONT_AND_BACK, FillModeMapping[value]);
			}
			break;

		case RenderState::CullMode:
			{
				if (state == Cull::None)
				{
					glDisable(GL_CULL_FACE);
				}
				else
				{
					glEnable(GL_CULL_FACE);
					glFrontFace(CullModeMapping[value]);
				}
			}
			break;

		case RenderState::ZEnable:
			{
				if (value == 0)
				{
					glDisable(GL_DEPTH_TEST);
				}
				else
				{
					glEnable(GL_DEPTH_TEST);
				}
			}
			break;

		case RenderState::ZWriteEnable:
			{
				if (value == 0)
				{
					glDepthMask(false);
				}
				else
				{
					glDepthMask(true);
				}
			}
			break;

		case RenderState::BlendEnable:
			{
				if (value == 0)
				{
					glDisable(GL_BLEND);
				}
				else
				{
					glEnable(GL_BLEND);
				}
			}
			break;

		case RenderState::LineWidth:
			{
				glLineWidth(Uint32ToFloat(value));
			}
			break;
		}
	}

	return true;
}

bool RenderSystem::beginScene()
{
	return true;
}

bool RenderSystem::endScene()
{
	return true;
}

const IntRect& OGLRenderSystem::getViewport() const
{
	return viewport_;
}

bool OGLRenderSystem::setViewport(const IntRect& viewport)
{
	viewport_ = viewport;
	return true;
}


void OGLRenderSystem::getColorMask(bool& red, bool& green, bool& blue, bool& alpha) const
{
	red = color_mask_[0];
	green = color_mask_[1];
	blue = color_mask_[2];
	alpha = color_mask_[3];
}

bool OGLRenderSystem::setColorMask(
	bool red, 
	bool green, 
	bool blue, 
	bool alpha)
{
	color_mask_[0] = red;
	color_mask_[1] = green;
	color_mask_[2] = blue;
	color_mask_[3] = alpha;

	glColorMask(red, green, blue, alpha);

	return true;
}

bool OGLRenderSystem::clear(uint32 flags, Color clr, float z)
{
	uint32 nFlagsAPI = 0;
	if (flags & Clear::Color)		nFlagsAPI |= GL_COLOR_BUFFER_BIT;
	if (flags & Clear::ZBuffer)		nFlagsAPI |= GL_DEPTH_BUFFER_BIT;
	
	if (!nFlagsAPI) return false; /** Error! */

	if (flags & Clear::Color)
	{
		glClearColor(clr.r_, clr.g_, clr.b_, clr.a_);
	}

	if (flags & Clear::ZBuffer) 
	{
		uint32 zwrite_enable = getRenderState(RenderState::ZWriteEnable);
		if (zwrite_enable) glClearDepth(z);
	}

	if (flags & Clear::Color)
	{
		glColorMask(true, true, true, true);
	}

	glClear(nFlagsAPI);

	return true;
}

RenderObject* OGLRenderSystem::getRenderTarget() const
{
	return curr_render_target_;
}

bool OGLRenderSystem::setRenderTarget(RenderObject* rtt)
{
	curr_render_target_ = rtt;

	return true;
}

IndexBuffer* OGLRenderSystem::getIndexBuffer()
{
	return curr_ib_;
}

bool OGLRenderSystem::setIndexBuffer(IndexBuffer* ibuffer)
{
	if (!ibuffer)
	{
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		return true;
	}

	if (curr_ib_ != ibuffer)
	{
		curr_ib_ = ibuffer;
		curr_ib_->bind();
	}

	return true;
}

VertexBuffer* OGLRenderSystem::getVertexBuffer()
{
	return curr_vb_;
}

bool OGLRenderSystem::setVertexBuffer(VertexBuffer* vbuffer)
{
	if (!vbuffer)
	{
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		return true;
	}

	if (curr_vb_ != vbuffer)
	{
		curr_vb_ = vbuffer;
		curr_vb_->bind();
	}

	return true;
}

bool OGLRenderSystem::drawPrimitives(uint32 type, uint32 start_idx, uint32 num_vertices)
{
	if (!num_vertices)
	{
		return true;
	}

	uint32 prim_count = 0;
	switch (type)
	{
	case Primitive::LineList:
		prim_count = num_vertices - 1;
		break;
	case Primitive::TriangleList:
		prim_count = num_vertices / 3;
		break;
	case Primitive::PointList:
		prim_count = num_vertices;
		break;
	default:
		HY_ASSERT(0);
		return false;
	}

	glDrawArrays(PrimModeMapping[type], start_idx, num_vertices);

	checkGLError();

	return true;
}

bool OGLRenderSystem::drawIndexedPrimitives(
	uint32 type, uint32 min_idx, uint32 max_idx, 
	uint32 start_idx, uint32 num_vertices)
{
	if (!curr_ib_) return false;
	if (!curr_vb_) return false;
	if (!num_vertices) return false;

	uint32 elem_size = 0;
	uint32 elem_type = curr_ib_->getElementType();
	switch (elem_type)
	{
	case IndexBuffer::UInt:
		elem_size = sizeof(uint32);
		elem_type = GL_UNSIGNED_INT;
		break;
	case IndexBuffer::UShort:
		elem_size = sizeof(uint16);
		elem_type = GL_UNSIGNED_SHORT;
		break;
	default:
		HY_ASSERT(0);
		return false;
	}

	uint32 prim_count = 0;
	switch (type)
	{
	case Primitive::LineList:
		prim_count = num_vertices - 1;
		break;
	case Primitive::TriangleList:
		prim_count = num_vertices / 3;
		break;
	case Primitive::PointList:
		prim_count = num_vertices;
		break;
	default:
		HY_ASSERT(0);
		return false;
	}

	glDrawElements(PrimModeMapping[type], num_vertices, elem_type, (void*)(start_idx * elem_size));

	checkGLError();

	return true;
}

}